import requests
from lxml import etree
import json
import subprocess
import os
from common.constant import ErrorInfo, RequestHeaders, DownloadType, XPath, CommonStrings
import re
from common.settings import ROOT_PATH, logger
from .decorator_control import timer


class BilibiliHandler:

    def __init__(self, url: str = '', download_type: int = DownloadType.VIDEO.value, is_delete_cache: bool = True):
        self.url = url
        self.headers = {
            'referer': RequestHeaders.REFERER.value,
            'User-Agent': RequestHeaders.USER_AGENT.value,
            'cookie': RequestHeaders.COOKIE.value
        }
        self.video_url = ''
        self.audio_url = ''
        self.title = ''
        self.video_cache_file_path = ''
        self.audio_cache_file_path = ''
        self.download_type = download_type
        # 默认删除临时文件，如果想保留：is_delete_cache=False
        self.is_delete_cache = is_delete_cache

    def check_url(self) -> None:
        if not self.url:
            logger.error(ErrorInfo.URL_IS_NONE.value)
            raise ValueError(ErrorInfo.URL_IS_NONE.value)
        pattern = r'^https://www.bilibili.com/video/'
        if not re.match(pattern, self.url):
            logger.error(ErrorInfo.URL_IS_WRONG.value)
            raise ValueError(ErrorInfo.URL_IS_WRONG.value)

    def get_title_and_url(self) -> None:
        try:
            page = requests.get(url=self.url, headers=self.headers).text
        except Exception as e:
            logger.error(e)
            raise e
        tree = etree.HTML(page)
        self.title = tree.xpath(XPath.TITLE.value)[0]
        self.re_title()
        logger.info(f'title: {self.title}')

        script_text = tree.xpath(XPath.SCRIPT_TEXT.value)[0]
        play_info = str(script_text).replace(CommonStrings.PLAY_INFO.value, '')
        play_info = json.loads(play_info)

        if self.download_type == DownloadType.VIDEO.value:
            # 2对应的编码方式为av1，画质略好一些，但需额外安装播放器才能播放，如不想额外安装播放器，可改为0，系统自带播放器即可播放
            self.video_url = play_info['data']['dash']['video'][2]['baseUrl']
            self.audio_url = play_info['data']['dash']['audio'][0]['baseUrl']
            logger.info(f'\nvideo_url: {self.video_url}\naudio_url: {self.audio_url}')
        else:
            self.audio_url = play_info['data']['dash']['audio'][0]['baseUrl']
            logger.info(f'audio_url: {self.audio_url}')

    @timer
    def download_video_or_audio(self) -> None:
        if self.download_type == DownloadType.VIDEO.value:
            self.video_cache_file_path = os.path.join(ROOT_PATH, 'cache', f'{self.title}-无音频.mp4')
            self.audio_cache_file_path = os.path.join(ROOT_PATH, 'cache', f'{self.title}.mp3')
            if not RequestHeaders.COOKIE.value:
                logger.warning(ErrorInfo.COOKIE_IS_NONE.value)
            try:
                logger.info(CommonStrings.DOWNLOADING.value)
                video = requests.get(url=self.video_url, headers=self.headers).content
                audio = requests.get(url=self.audio_url, headers=self.headers).content
            except Exception as e:
                logger.error(e)
                raise e
            with open(self.video_cache_file_path, 'wb') as fp:
                fp.write(video)
            logger.info(f'视频：{self.title}-无音频.mp4 下载完毕')
            with open(self.audio_cache_file_path, 'wb') as fp:
                fp.write(audio)
            logger.info(f'音频：{self.title}.mp3 下载完毕')
        elif self.download_type == DownloadType.AUDIO.value:
            output_file_path = os.path.join(ROOT_PATH, 'output', f'{self.title}.mp3')
            try:
                logger.info(CommonStrings.DOWNLOADING.value)
                audio = requests.get(url=self.audio_url, headers=self.headers).content
            except Exception as e:
                logger.error(e)
                raise e
            with open(output_file_path, 'wb') as fp:
                fp.write(audio)
            logger.info(f'音频：{self.title}.mp3 下载完毕')
        else:
            logger.error(ErrorInfo.DOWN_LOAD_TYPE_IS_WRONG.value)
            raise ValueError(ErrorInfo.DOWN_LOAD_TYPE_IS_WRONG.value)

    def compose_video_and_audio(self) -> None:
        output_file_path = os.path.join(ROOT_PATH, 'output', f'{self.title}.mp4')
        if self.download_type == DownloadType.VIDEO.value:
            if os.path.exists(self.video_cache_file_path):
                if os.path.exists(self.audio_cache_file_path):
                    # 让声音提前0.2秒，否则会音画不同步，声音比视频慢（具体调整时间因视频而异）
                    cmd = f'ffmpeg -i {self.video_cache_file_path} -itsoffset 0.2 -i {self.audio_cache_file_path} ' \
                          f'-c copy {output_file_path}'
                    logger.info(f'执行合成命令：{cmd}')
                    try:
                        subprocess.call(cmd, shell=True)
                        logger.info('音视频合成完毕')
                    except Exception as e:
                        logger.error(e)
                        raise e
                else:
                    logger.error(f'{self.audio_cache_file_path}不存在')
                    raise ValueError(f'{self.audio_cache_file_path}不存在')
            else:
                logger.error(f'{self.video_cache_file_path}不存在')
                raise ValueError(f'{self.video_cache_file_path}不存在')
        else:
            return None

    def remove_cache_file(self) -> None:
        if self.download_type == DownloadType.VIDEO.value and self.is_delete_cache:
            try:
                os.remove(f'{self.video_cache_file_path}')
                os.remove(f'{self.audio_cache_file_path}')
                logger.info('已删除临时文件')
            except Exception as e:
                logger.error(e)
                raise e
        else:
            return None

    def re_title(self) -> None:
        # 删除标题中的空格
        self.title = ''.join(self.title.split())
        # 用_替换标题中的特殊字符
        spe_char_lst = ['\'', '/', ':', '*', '?', '"', '<', '>', '|']
        for i in range(len(self.title)):
            for ch in spe_char_lst:
                if self.title[i] == ch:
                    self.title = self.title.replace(self.title[i], '_')
        self.title = self.title.replace(CommonStrings.REDUNDANT_STR.value, '')

    def run(self) -> None:
        logger.info('***** start *****')
        self.check_url()
        self.get_title_and_url()
        self.download_video_or_audio()
        self.compose_video_and_audio()
        self.remove_cache_file()
        logger.info('***** end *****')
